"""Holds, shuffles, and deals out tiles."""

__author__ = "Gabriel Taubman (gtaubman@mac.com)"

import random
import unittest
from constants import BLANK_TILE

class TileBag:
  """A class simulating a bag of tiles.

  A tile bag contains all the tiles for a given game.  It is initialized
  with a dictionary keyed by tile with values of how many of that tile you'd
  like to be in the bag.  For example:
    {"A,B,C" : 1, "D" : 2}
  would create a tile bag with 1 A, 1 B, 1 C, and 2 D's.
  
  If no configuration is specified, it uses the standard scrabble set of 100
  tiles:
    1 of K, J, X, Q, and Z
    2 of B, C, M, P, F, H, V, W, Y, and blank
    3 of G
    4 of L, S, U and D
    6 of N, R and T
    8 of O
    9 of A and I
    12 of E
  """

  def __init__(self, config=None):
    """Initialize a tile bag with a tile configuration.

    Params:
      config: A dictionary where the keys are strings containing comma-separated
              tile names, and the value is how many of each of those tiles to
              place in the bag."""
    # Keep a copy of the configuration incase we're asked to reset ourselves.
    self.config = config
    if not self.config:
      # We use a default configuration if none is specified.
      self.config = {"K,J,X,Q,Z" : 1,
                     BLANK_TILE + ",B,C,M,P,F,H,V,W,Y" : 2,
                     "G" : 3,
                     "L,S,U,D" : 4,
                     "N,R,T" : 6,
                     "O" : 8,
                     "A,I" : 9,
                     "E" : 12}

    # Initialize our tile "bag".
    self.tiles = []
    for tiles, num in self.config.iteritems():
      for tile in tiles.split(","):
        for i in range(num):
          self.tiles.append(tile)

    # Shuffle the bag.
    random.shuffle(self.tiles)

  def Draw(self):
    """Returns a tile, or None if there are no tiles left."""
    if self.tiles:
      return self.tiles.pop()
    return None

  def AddTile(self, letter):
    """Inserts letter into the tile bag and reshuffles the bag."""
    self.tiles.append(letter)
    random.shuffle(self.tiles)

  def RemainingTiles(self):
    """Returns how many tiles are remaining."""
    return len(self.tiles)

  def __iter__(self):
    return self

  def next(self):
    drawn_tile = self.Draw()
    if not drawn_tile:
      raise StopIteration
    return drawn_tile


class TileBagTest(unittest.TestCase):
  def testDefaultConfiguration(self):
    # Create a default bag.
    tile_bag = TileBag()

    # By default there should be 100 tiles.
    for i in range(100):
      drawn_tile = tile_bag.Draw()
      self.assertNotEqual(drawn_tile, None, "Drew a None after %d tiles." % i)

    # Now that we've drawn 100, drawing any more should give us a None tile.
    for i in range(10):
      drawn_tile = tile_bag.Draw()
      self.assertEquals(drawn_tile,
                       None,
                       "Should have drawn a None, "
                       "but instead got %s" % drawn_tile)

  def testTwoBagsAreDifferent(self):
    # Get two bags of tiles.
    bag1 = TileBag()
    bag2 = TileBag()

    # Make sure they have the same number of tiles.
    num_bag1_tiles = bag1.RemainingTiles()
    num_bag2_tiles = bag2.RemainingTiles()
    self.assertEquals(num_bag1_tiles,
                     num_bag2_tiles,
                     "Bag 1 had %d tiles, and bag 2 had %d tiles." %
                       (num_bag1_tiles, num_bag2_tiles))

    # Draw all the tiles out and make sure they're in a different order.
    bag1_tiles = [tile for tile in bag1]
    bag2_tiles = [tile for tile in bag2]
    self.assert_(bag1_tiles != bag2_tiles,
                 "Bag1: %s, Bag2: %s" % (bag1_tiles, bag2_tiles))

  def testCustomTileConfig(self):
    tile_bag = TileBag({"A,B,C" : 1, "D" : 2})
    tile_counts = { "A" : 0, "B" : 0, "C" : 0, "D" : 0}
    
    tiles_found = 0
    for tile in tile_bag:
      tiles_found += 1
      tile_counts[tile] += 1

    self.assertEquals(tiles_found, 5)
    self.assertEquals(tile_counts["A"], 1)
    self.assertEquals(tile_counts["B"], 1)
    self.assertEquals(tile_counts["C"], 1)
    self.assertEquals(tile_counts["D"], 2)

  def testEmptyCustomTileConfig(self):
    tile_bag = TileBag({ "" : 0})
    for i in range(10):
      self.assertEquals(tile_bag.Draw(), None)

    tile_bag = TileBag({"A,B,C" : 0})
    for i in range(10):
      self.assertEquals(tile_bag.Draw(), None)

  def testAddingTile(self):
    tile_bag = TileBag()
    num_pieces = tile_bag.RemainingTiles()

    tile_bag.AddTile("A")
    self.assert_(tile_bag.RemainingTiles() == num_pieces + 1)

if __name__ == "__main__":
  unittest.main()
